package wci.frontend.pascal.tokens;

import wci.frontend.*;
import wci.frontend.pascal.*;

import static wci.frontend.pascal.PascalTokenType.*;
import static wci.frontend.pascal.PascalErrorCode.*;

/**
 * <h1>PascalNumberToken</h1>
 *
 * <p>Pascal 数字Token (整数或实数).</p>
 *
 * <p>Copyright (c) 2009 by Ronald Mak</p>
 * <p>For instructional purposes only.  No warranties.</p>
 */
public class PascalNumberToken extends PascalToken
{
    private static final int MAX_EXPONENT = 37;

    public PascalNumberToken(Source source)
        throws Exception
    {
        super(source);
    }

    /**
     * 从source中抽取一个数字
     * @throws Exception
     */
    protected void extract()
        throws Exception
    {
        StringBuilder textBuffer = new StringBuilder();  // token's characters
        extractNumber(textBuffer);
        text = textBuffer.toString();
    }

    /**
     * 实际的抽取操作
     * @param textBuffer 字符缓冲区
     * @throws Exception
     */
    protected void extractNumber(StringBuilder textBuffer)
        throws Exception
    {
        String wholeDigits = null;     // 小数点前的数字
        String fractionDigits = null;  // 小数点后的数字
        String exponentDigits = null;  // 标识指数的数字
        char exponentSign = '+';       // 指数标识 + 或-，默认为+
        boolean sawDotDot = false;     // 是否有 .. Token
        char currentChar;              // 当前字符

        type = INTEGER;  // 默认为整数

        //首先取整数部分的数字，比如34.15，则取 34
        wholeDigits = unsignedIntegerDigits(textBuffer);
        if (type == ERROR) {
            return;
        }

        // 接下来是否出现 .
        // 可能是个小数点 . 也可能是表示range .. 的第一个点
        currentChar = currentChar();
        if (currentChar == '.') {
            if (peekChar() == '.') {
                sawDotDot = true;  //表示 range的..，不再抽取，直接计算值
            }
            else {
                type = REAL;  // 带点表示实数
                textBuffer.append(currentChar);
                currentChar = nextChar(); 

                //抽取数的小数部分比如34.15，则抽取 15
                fractionDigits = unsignedIntegerDigits(textBuffer);
                if (type == ERROR) {
                    return;
                }
            }
        }

        // 是否有指数部分比如数可以为34.15E14
        currentChar = currentChar();
        if (!sawDotDot && ((currentChar == 'E') || (currentChar == 'e'))) {
            type = REAL;  //带指数，肯定是实数
            textBuffer.append(currentChar);
            currentChar = nextChar(); 

            // 指数有正负符号位？
            if ((currentChar == '+') || (currentChar == '-')) {
                textBuffer.append(currentChar);
                exponentSign = currentChar;
                currentChar = nextChar(); 
            }

            // 抽取指数部分数字，34.15E14则抽取 14
            exponentDigits = unsignedIntegerDigits(textBuffer);
        }

        //计算整数值
        if (type == INTEGER) {
            int integerValue = computeIntegerValue(wholeDigits);

            if (type != ERROR) {
                value = new Integer(integerValue);
            }
        }

        //计算实数(也就是浮点数）值
        else if (type == REAL) {
            float floatValue = computeFloatValue(wholeDigits, fractionDigits,
                                                 exponentDigits, exponentSign);

            if (type != ERROR) {
                value = new Float(floatValue);
            }
        }
    }

    /**
     * 抽取并返回无符号整数的数字，即不停遍历直到不是[0-9]为止。
     * @param textBuffer 字符缓冲
     * @return 数字串
     * @throws Exception
     */
    private String unsignedIntegerDigits(StringBuilder textBuffer)
        throws Exception
    {
        char currentChar = currentChar();

        // 如果抽取数字，则至少有一个数字位，否则有问题。
        if (!Character.isDigit(currentChar)) {
            type = ERROR;
            value = INVALID_NUMBER;
            return null;
        }

        //遍历数字
        StringBuilder digits = new StringBuilder();
        while (Character.isDigit(currentChar)) {
            textBuffer.append(currentChar);
            digits.append(currentChar);
            currentChar = nextChar();  // consume digit
        }

        return digits.toString();
    }

    /**
     * 计算和返回数字Token，其类型为整数(没有小数点)的值
     * Check for overflow.
     * @param digits the string of digits.
     * @return the integer value.
     */
    private int computeIntegerValue(String digits)
    {
        if (digits == null) {
            return 0;
        }

        int integerValue = 0;
        int prevValue = -1;    // 如果有溢出发生，-1为正溢出的最大值
        int index = 0;

        //根据十进制数每位权值计算其值，比如123 = 100*1+20*2+3 => 10(10*(1)+2)+3，广义表
        while ((index < digits.length()) && (integerValue >= prevValue)) {
            prevValue = integerValue;
            integerValue = 10*integerValue +
                           Character.getNumericValue(digits.charAt(index++));
        }

        // 正常无溢出
        if (integerValue >= prevValue) {
            return integerValue;
        }

        else {
            type = ERROR;
            value = RANGE_INTEGER;
            return 0;
        }
    }

    /**
     * 计算浮点数的数值
     * @param wholeDigits 整数部分
     * @param fractionDigits 小数部分
     * @param exponentDigits 指数部分
     * @param exponentSign 指数正负符号位
     * @return 浮点值
     */
    private float computeFloatValue(String wholeDigits, String fractionDigits,
                                    String exponentDigits, char exponentSign)
    {
        double floatValue = 0.0;
        int exponentValue = computeIntegerValue(exponentDigits);
        String digits = wholeDigits;

        if (exponentSign == '-') {
            exponentValue = -exponentValue;
        }

        //如果有小数，则指数降幂，而小数变整，比如 10.1E2，表示10.1*10^2=1010相当于101*10^1即101E1
        if (fractionDigits != null) {
            exponentValue -= fractionDigits.length();
            digits += fractionDigits;
        }

        // 超位
        if (Math.abs(exponentValue + wholeDigits.length()) > MAX_EXPONENT) {
            type = ERROR;
            value = RANGE_REAL;
            return 0.0f;
        }

        int index = 0;
        while (index < digits.length()) {
            floatValue = 10*floatValue +
                         Character.getNumericValue(digits.charAt(index++));
        }
        if (exponentValue != 0) {
            floatValue *= Math.pow(10, exponentValue);
        }

        return (float) floatValue;
    }
}
